# -*- coding: utf-8 -*-

'''
/**
 * @addtogroup az_tcp_server
 * az_tcp_server
 * @{
 */
/**
 * @file        az_tcp_server.py
 * @brief       XXXX
 * @note        XXXX
 * @author      靳普诏(puzhao.jin@hopechart.com)
 * @date        2023/09/21
 * @version     1.0

 * @par         修改日志
 * <table>
 * <tr><th>Date         <th>Version     <th>Author      <th> Description
 * <tr><td>2023/09/21   <td>1.0         <td>靳普诏       <td> 创建初始版本
 * @copyright   xxxx
 */
'''

import time
import sys
import os
import socket
from threading import Thread
from threading import Lock
from abc import ABC, abstractmethod
from enum import Enum
import traceback

class TAzSocketDisconnectType(Enum):
    kUnknown = 0
    kClientActive = 1
    kTimeout = 2
    kShutdown = 3

class TAzSocketServerStateType(Enum):
    kStopped = 0
    kStarted = 1

class TAzTcpServerBase(ABC):

    def __init__(self, host = "127.0.0.1", port = 60006, listen_cnt = 1, connect_timeout_sec = 30):
        self.host = host
        self.port = port
        self.connect_timeout_sec = connect_timeout_sec
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.bind((host, port))
        self.socket.listen(listen_cnt)
        self.client_sockets = {}
        self.client_thread = {}
        self.state = TAzSocketServerStateType.kStopped
        self.recv_buff_size = 1024
        self.__lock = Lock()
        return

    def _listen_client(self):
        try:
            while True:
                client_socket, client_addr = self.socket.accept() 
                if self.state == TAzSocketServerStateType.kStopped:
                    break
                # 添加socket到字典,以客户端地址作为key
                self.client_sockets[client_addr] = client_socket

                # 启动新的线程处理客户端通信
                client_thread = Thread(target=self._handle_client, args=(client_socket,client_addr))
                client_thread.start()
                self.client_thread[client_addr] = client_thread
        finally:
            print("_listen_client End")
            

    def _handle_client(self, client_socket: socket.socket, client_addr:str):
        # 设置30分钟超时
        client_socket.settimeout(1)
        self.OnClientConnect(client_socket, client_addr)
        timeout_count = 0
        
        while self.state != TAzSocketServerStateType.kStopped:
            self.OnClientRunOnce(client_socket, client_addr)
            try:
                data = client_socket.recv(self.recv_buff_size)
                if not data:
                    # 主动断开
                    log = 'active disconnection'
                    self._on_disconnect(client_addr, log)
                    self.OnClientDisconnect(client_addr, TAzSocketDisconnectType.kClientActive, log)
                    break
                
                # 处理数据
                self.OnClientRecv(client_socket, client_addr, data)
                timeout_count = 0

            except socket.timeout:
                timeout_count += 1
                if timeout_count >= self.connect_timeout_sec:
                    # 长时间无消息，主动断开
                    log = f'idle {self.connect_timeout_sec / 60}min timeout'
                    self._on_disconnect(client_addr,log)
                    self.OnClientDisconnect(client_addr, TAzSocketDisconnectType.kTimeout, log)
                    break

            except Exception as e:
                # 打印异常信息
                print('遇到错误:', e)  
                traceback.print_exc()
                # 被动断开
                log = 'except unknown'
                self._on_disconnect(client_addr, log)
                self.OnClientDisconnect(client_addr, TAzSocketDisconnectType.kUnknown, log)
                break
            
            except KeyboardInterrupt:
                log = 'Shut down'
                print(f"log = {log}")
                self._on_disconnect(client_addr, log)
                self.OnClientDisconnect(client_addr, TAzSocketDisconnectType.kShutdown, log)
                break
        

    def _on_disconnect(self, client_addr, disconnect_type: TAzSocketDisconnectType):
        self.client_sockets[client_addr].shutdown(socket.SHUT_WR)
        # print(f'Client {client_addr} disconnected, type: {disconnect_type}')
        self.client_sockets.pop(client_addr)

    def LockThread(self):
        self.__lock.acquire()
    
    def UnlockThread(self):
        self.__lock.release()

    def Send(self, client_addr: str, send_data: bytes):
        client_socket = self.client_sockets[client_addr]
        result = client_socket.send(send_data)
        self.OnClientSend(client_socket, client_addr, send_data, result)
        return result
    
    def Start(self, join_timeout = None):
        # 启动线程监听客户端连接
        self.LockThread()
        self.state = TAzSocketServerStateType.kStarted
        self.UnlockThread()
        self.listen_thread = Thread(target=self._listen_client)
        self.listen_thread.start()
        
    
    def Stop(self):
        self.LockThread()
        self.state = TAzSocketServerStateType.kStopped
        stop = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        stop.connect((self.host, self.port))

        print("------------- 服务器停止运行 -------------")
        self.UnlockThread()

    def Block(self):
        try:
            while True:  
                print("Main thread is running...")  
                if input("If you want to stop the script, please input 'q'\n\n").lower() == "q":  
                    break
        finally:
            self.Stop()
            self.listen_thread.join(30)
            sys.exit()  # 停止脚本的执行 
        

    @abstractmethod
    def OnClientRecv(self, client_socket: socket.socket, client_addr:str, client_recv_data: bytes):
        return
    
    @abstractmethod
    def OnClientSend(self, client_socket: socket.socket, client_addr:str, client_send_data: bytes, client_send_result):
        return

    @abstractmethod
    def OnClientConnect(self, client_socket: socket.socket, client_addr:str):
        return

    @abstractmethod
    def OnClientDisconnect(self, client_addr:str, disconnect_type:TAzSocketDisconnectType, log:str):
        return
    
    @abstractmethod
    def OnClientRunOnce(self, client_socket: socket.socket, client_addr:str):
        return
    


class TSocketServerTemplater(TAzTcpServerBase):
    def __init__(self, host="127.0.0.1", port=60006, listen_cnt=1, connect_timeout_sec=1800):
        super().__init__(host, port, listen_cnt, connect_timeout_sec)
        return
    
    # @abstractmethod
    def OnClientConnect(self, client_socket: socket.socket, client_addr: str):
        print(f"[OnClientConnect] client_socket_addr:{client_addr}")
    
    # @abstractmethod
    def OnClientDisconnect(self, client_addr: str, disconnect_type: TAzSocketDisconnectType, log: str):
        print(f"[OnClientDisconnect] client_socket_addr:{client_addr}, type:{log}, type_id:{disconnect_type.name}")
    
    # @abstractmethod
    def OnClientSend(self, client_socket: socket.socket, client_addr: str, send_data: bytes, send_result):
        print(f"[OnClientSend] >>> client_socket_addr:{client_addr}, send_result:{send_result}, data:{send_data}")
    
    # @abstractmethod
    def OnClientRecv(self, client_socket: socket.socket, client_addr: str, recv_data: bytes):
        print(f"[OnClientRecv] <<< client_socket_addr:{client_addr}, recv_data:{recv_data}")
        self.Send(client_addr, recv_data)

    # @abstractmethod
    def OnClientRunOnce(self, client_socket: socket.socket, client_addr:str):
        return


if __name__ == "__main__":
    ## 获取当前脚本所在的目录
    SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
    # 输出目录路径
    print("SCRIPT_DIR: "+ SCRIPT_DIR)

    ## 获取脚本传参
    argv = sys.argv
    argc = len(argv)
    # 打印命令行参数
    #for i in range(1, argc):
        #print("参数", i, ":", argv[i])
        
    print("============= BIGEN: az_tcp_server.py =============")

    server = TSocketServerTemplater("127.0.0.1", 9999)

    server.Start()
    server.Block()


    print("============= END: az_tcp_server.py =============")



## Generated on "2023-09-21 23:50:36" by the tool "gen_hq_file.py >> V20230509_1" 